package com.xp.tinyurl.api;

import com.xp.tinyurl.common.base.ResultVO;
import com.xp.tinyurl.common.base.ResultVOBuilder;
import com.xp.tinyurl.common.cache.AppCache;
import com.xp.tinyurl.common.cache.LongUrlCache;
import com.xp.tinyurl.common.cache.ShortUrlCache;
import com.xp.tinyurl.common.cache.ShorterUrlCountCache;
import com.xp.tinyurl.common.dto.ChangeLongUrlDTO;
import com.xp.tinyurl.common.dto.ExtendValidDTO;
import com.xp.tinyurl.common.dto.UShorter;
import com.xp.tinyurl.common.dto.UShorterUrlDTO;
import com.xp.tinyurl.common.enums.ExpiredEnum;
import com.xp.tinyurl.common.utils.BinaryUtil;
import com.xp.tinyurl.common.utils.ExpiredEnumUtil;
import com.xp.tinyurl.model.Register;
import com.xp.tinyurl.model.ShortUrlMap;
import com.xp.tinyurl.service.AppRegisterService;
import com.xp.tinyurl.service.ShortUrlMapSddlService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.text.MessageFormat;
import java.util.Date;

/**
 * 短URL管理（非页面接口）
 */
@Slf4j
@RestController
@RequestMapping("/api/url")
public class TinyUrlApi {
    private static final Logger logger = LoggerFactory.getLogger(TinyUrlApi.class);

    @Autowired
    private AppRegisterService appRegisterService;

    @Autowired
    private ShortUrlMapSddlService shortUrlMapSddlService;

    @Autowired
    private AppCache appCache;

    @Autowired
    private LongUrlCache longUrlCache;

    @Autowired
    private ShorterUrlCountCache shorterUrlCountCache;

    @Autowired
    private ShortUrlCache shortUrlCache;

    /**
     * 延长短链接有效性
     * @param extendValidDTO
     * @return
     */
    @RequestMapping("/extendValid")
    public ResultVO extendValid(@Valid ExtendValidDTO extendValidDTO){
        log.info("params => {}", JSONObject.toJSONString(extendValidDTO));
        //验证 当前时间短链接情况
        String currentTime = DateFormatUtils.format(new Date(),"yyyyMMddHHmm");
        ResultVO<Register> resultVo = validateApp(extendValidDTO.getAppKey(), currentTime);
        if(resultVo != null && !resultVo.isSuccess()){
            return resultVo;
        }
        //valid tinyurl exist
        ShortUrlMap shortUrlMap = shortUrlMapSddlService.getByShortUrl(getUuidByTinyUrl(extendValidDTO.getTinyUrl()));
        if(shortUrlMap == null){
            return ResultVOBuilder.failure("短链接地址不存在！");
        }
        //build update entity
        ShortUrlMap shortUrlMapUpdate = new ShortUrlMap();
        shortUrlMapUpdate.setId(shortUrlMap.getId());
        shortUrlMapSddlService.updateExpireTime(shortUrlMapUpdate, ExpiredEnum.getByCode(extendValidDTO.getExpired()));

        //统计访问成功量
        shorterUrlCountCache.incrementSuccessCount(extendValidDTO.getAppKey(), currentTime);
        return ResultVOBuilder.success();
    }

    /**
     * 修改短连接对应长连接地址
     * @param changeLongUrlDTO
     * @return
     */
    @RequestMapping("/changeLongUrl")
    public ResultVO changeLongUrl(@Valid ChangeLongUrlDTO changeLongUrlDTO){
        log.info("params => {}", JSONObject.toJSONString(changeLongUrlDTO));
        //验证 当前时间短链接情况
        String currentTime = DateFormatUtils.format(new Date(),"yyyyMMddHHmm");
        ResultVO<Register> resultVo = validateApp(changeLongUrlDTO.getAppKey(), currentTime);
        if(resultVo != null && !resultVo.isSuccess()){
            return resultVo;
        }
        //valid tinyurl exist
        long uuid = getUuidByTinyUrl(changeLongUrlDTO.getTinyUrl());
        ShortUrlMap shortUrlMap = shortUrlMapSddlService.getByShortUrl(uuid);
        if(shortUrlMap == null){
            return ResultVOBuilder.failure("短链接地址不存在！");
        }
        //build update entity
        ShortUrlMap shortUrlMapUpdate = new ShortUrlMap();
        shortUrlMapUpdate.setId(shortUrlMap.getId());
        shortUrlMapSddlService.updateLongUrl(shortUrlMapUpdate, changeLongUrlDTO.getLongUrl());

        /*
         *根据缓存更新套路，名为《Cache-Aside pattern》。其中就指出
         * 失效：应用程序先从cache取数据，没有得到，则从数据库中取数据，成功后，放到缓存中。
         * 命中：应用程序从cache中取数据，取到后返回。
         * 更新：先把数据存到数据库中，成功后，再让缓存失效。
         * 此方案适合低写入场景,也可使用  异步延时删除策略 机制进行删除
         */
        shortUrlCache.remove(uuid);

        //统计访问成功量
        shorterUrlCountCache.incrementSuccessCount(changeLongUrlDTO.getAppKey(), currentTime);
        return ResultVOBuilder.success();
    }


    /**
     * Description: 缩短网址接口
     *
     * @param shorterUrlDTO 参数
     * @return String 缩短后的短链接url
     */
    @RequestMapping("/genTinyUrl")
    public ResultVO<UShorter> genTinyUrl(@Valid UShorterUrlDTO shorterUrlDTO){
        //验证 当前时间短链接情况
        String currentTime = DateFormatUtils.format(new Date(),"yyyyMMddHHmm");
        ResultVO<Register> resultVo = validateApp(shorterUrlDTO.getAppKey(), currentTime);
        if(resultVo != null && !resultVo.isSuccess()){
            ResultVO uShorterVO = new ResultVO();
            BeanUtils.copyProperties(resultVo, uShorterVO);
            return uShorterVO;
        }
        try {
            //缓存中若存在 则直接返回
            ShortUrlMap shortUrlMap = longUrlCache.getShortUrl(shorterUrlDTO.getLongUrl());
            if (shortUrlMap != null) {
                logger.trace("cache hit for longUrl:{}", shorterUrlDTO.getLongUrl());
                return ResultVOBuilder.success(buildShortUrlMap2UShorter(shortUrlMap));
            }
            //设置统计访问量
            shorterUrlCountCache.incrementTotalCount(shorterUrlDTO.getAppKey(), currentTime);
            //创建短链
            shorterUrlDTO.setBusinessLine(resultVo.getData().getBusinessLine());
            ShortUrlMap shortUrlMapDB = shortUrlMapSddlService.createTinyUrl(shorterUrlDTO);
            if(shortUrlMapDB == null){
                logger.error("use longUrl as default for no suitable shortUrl");
                return ResultVOBuilder.success(buildFailByDefault(shorterUrlDTO));
            }
            //统计访问成功量
            shorterUrlCountCache.incrementSuccessCount(shorterUrlDTO.getAppKey(), currentTime);
            return ResultVOBuilder.success(buildShortUrlMap2UShorter(shortUrlMapDB));
        }catch (Exception e) {
            logger.error("fail to generate shortUrl, request:{}", JSON.toJSONString(shorterUrlDTO), e);
        }
        logger.error("use longUrl as default for no suitable shortUrl");
        return ResultVOBuilder.success(buildFailByDefault(shorterUrlDTO));
    }

    /**
     * 验证应用侧
     * @param appKey
     * @param currentTime
     * @return
     */
    private ResultVO<Register> validateApp(String appKey, String currentTime) {
        //从缓存加载
        Register register = appCache.getByAppKey(appKey);
        if (register == null) {
            //从db加载
            register = appRegisterService.getAppRegisterByAppKey(appKey);
            if(register == null){
                return ResultVOBuilder.failure("未能识别的appKey，请联系管理员！");
            }
            //回写缓存
            appCache.add(register.getAppKey(), register);
        }
        if(register.getDeleted()){
            return ResultVOBuilder.failure(MessageFormat.format("{0}({1})已下线，请联系管理员！", register.getModule(), register.getAppKey()));
        }
        if (register.getLocked()) {
            return ResultVOBuilder.failure(MessageFormat.format("{0}({1})已被锁定，请联系管理员！", register.getModule(), register.getAppKey()));
        }
        // 熔断机制
        if (register.getHystrix()) {
            long totalCount = shorterUrlCountCache.getTotalCountMinute(appKey, currentTime);
            if (totalCount >= register.getThreshold()) {
                //ErrorCodeEnum.INTERNAL_ERROR
                return ResultVOBuilder.failure(MessageFormat.format("{0}({1})熔断，请联系管理员！", register.getModule(), register.getAppKey()));
            }
        }
        return ResultVOBuilder.success(register);
    }

    private long getUuidByTinyUrl(String tinyUrl){
        return BinaryUtil.toBase10(tinyUrl.substring(tinyUrl.lastIndexOf("/")+1));
    }

    /**
     * ShortUrlMapPO转换为DTO
     *
     * @param shortUrlMap
     * @return
     */
    private UShorter buildShortUrlMap2UShorter(ShortUrlMap shortUrlMap) {
        UShorter uShorter = new UShorter();
        uShorter.setLongUrl(shortUrlMap.getLongUrl());
        uShorter.setShortUrl(shortUrlMap.getShortUrl());
        uShorter.setExpired(shortUrlMap.getExpireTime().getTime());
        return uShorter;
    }

    /**
     * 生成失败，默认使用原链接
     * @param shorterUrlDTO
     * @return
     */
    private UShorter buildFailByDefault(UShorterUrlDTO shorterUrlDTO) {
        UShorter uShorter = new UShorter();
        uShorter.setLongUrl(shorterUrlDTO.getLongUrl());
        uShorter.setShortUrl(shorterUrlDTO.getLongUrl());
        uShorter.setExpired(ExpiredEnumUtil.toTimestamp(ExpiredEnum.FOREVER));
        logger.warn("use longUrl:{} as default for no suitable shortUrl for appKey:{}", uShorter.getLongUrl(), shorterUrlDTO.getAppKey());
        return uShorter;
    }


}
